package com.nageoffer.shortlink.gateway.filter;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.nageoffer.shortlink.gateway.config.Config;
import com.nageoffer.shortlink.gateway.dto.GatewayErrorResult;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import reactor.core.publisher.Mono;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;

/**
 * SpringCloud Gateway Token 拦截器
 */
@Component
public class TokenValidateGatewayFilterFactory extends AbstractGatewayFilterFactory<Config> {

    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 构造方法注入stringRedisTemplate
     * @param stringRedisTemplate redis操作对象
     */
    public TokenValidateGatewayFilterFactory(StringRedisTemplate stringRedisTemplate) {
        super(Config.class);
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 重写apply方法以应用网关过滤逻辑
     * 该方法根据配置动态生成过滤逻辑，主要用于鉴权和请求改写
     *
     * @param config 过滤器配置，包含白名单和Redis模板等信息
     * @return 返回一个GatewayFilter，用于处理进入网关的请求
     */
    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            // 获取当前请求
            ServerHttpRequest request = exchange.getRequest();
            // 将请求路径和方法转换为字符串，用于后续的白名单匹配和日志记录
            String requestPath = request.getPath().toString();
            String requestMethod = request.getMethod().name();
            // 检查请求路径和方法是否在白名单中，如果不在则进行鉴权
            if (!isPathInWhiteList(requestPath, requestMethod, config.getWhitePathList())) {
                // 从请求头中获取用户名和令牌
                String username = request.getHeaders().getFirst("username");
                String token = request.getHeaders().getFirst("token");
                // 验证用户名和令牌，如果有效则添加用户信息到请求头并继续执行链
                Object userInfo;
                // hasText()判断给定的字符串是否为非空且包含非空白字符
                if (StringUtils.hasText(username) && StringUtils.hasText(token) && (userInfo = stringRedisTemplate.opsForHash().get("short-link:login:" + username, token)) != null) {
                    // 解析用户信息为JSON对象
                    JSONObject userInfoJsonObject = JSON.parseObject(userInfo.toString());
                    // 创建请求构建器以修改请求头
                    ServerHttpRequest.Builder builder = exchange.getRequest().mutate().headers(httpHeaders -> {
                        // 将用户ID和真实姓名添加到请求头中
                        httpHeaders.set("userId", userInfoJsonObject.getString("id"));
                        httpHeaders.set("realName", URLEncoder.encode(userInfoJsonObject.getString("realName"), StandardCharsets.UTF_8));
                    });
                    // 使用修改后的请求继续执行链
                    return chain.filter(exchange.mutate().request(builder.build()).build());
                }
                // 如果鉴权失败，返回401状态码和错误信息
                ServerHttpResponse response = exchange.getResponse();
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.writeWith(Mono.fromSupplier(() -> {
                    DataBufferFactory bufferFactory = response.bufferFactory();
                    GatewayErrorResult resultMessage = GatewayErrorResult.builder()
                            .status(HttpStatus.UNAUTHORIZED.value())
                            .message("Token validation error")
                            .build();
                    return bufferFactory.wrap(JSON.toJSONString(resultMessage).getBytes());
                }));
            }
            // 如果请求路径在白名单中，直接继续执行链，不做鉴权
            return chain.filter(exchange);
        };
    }

    /**
     * 判断请求路径和方法是否在白名单中
     *
     * @param requestPath 请求路径，用于与白名单中的路径进行匹配
     * @param requestMethod 请求方法，用于与特定路径关联的方法进行比较
     * @param whitePathList 白名单路径列表，包含一组被允许无需认证的路径
     * @return 如果请求路径和方法匹配白名单中的任意一项，则返回true，否则返回false
     */
    private boolean isPathInWhiteList(String requestPath, String requestMethod, List<String> whitePathList) {
        // 检查白名单列表是否非空，并且请求路径以白名单中的任意路径开始，或者检查特定的路径和方法是否匹配预设的白名单条件
        return (!CollectionUtils.isEmpty(whitePathList)&& whitePathList.stream().anyMatch(requestPath::startsWith))
                || (Objects.equals(requestPath, "/api/shortlink/admin/v1/user") && Objects.equals(requestMethod, "POST"));
    }
}